use std::fmt::UpperHex;
use std::process::Stdio;
use std::sync::OnceLock;
use std::time::Duration;
use tokio::io::AsyncReadExt;
use tokio::process::Command;
use tokio::time::timeout;

pub const SHELLS: [&str; 3] = ["zsh", "bash", "sh"];
pub static SHELL: OnceLock<&str> = OnceLock::new();

fn get_shell_inner() -> &'static str {
    match std::env::consts::OS {
        "macos" | "linux" => {
            for shell in SHELLS.iter() {
                let result = std::process::Command::new(shell)
                    .arg("-c")
                    .arg(String::new() + "type " + shell)
                    .output();
                if result.is_ok() && result.unwrap().status.success() {
                    return shell;
                }
            }
            ""
        }
        "windows" => {
            if let Ok(output) = std::process::Command::new("where").arg("cmd").output() {
                if output.status.success() { "cmd" } else { "" }
            } else {
                ""
            }
        }
        _ => {
            panic!("Unsupported OS");
        }
    }
}

/// 获取系统可用的shell，如果返回空字符串，表示没有可用的shell
pub fn get_shell() -> &'static str {
    SHELL.get_or_init(get_shell_inner)
}

/// cmd shell命令 secs 执行时长，单位秒，超过该时长会杀死进程 <br>
/// 返回值i32为命令的执行结果代码，vec是输出，最后一个字符通常是'\n' <br>
pub async fn run_cmd(cmd: &str, secs: u64) -> Result<(i32, Vec<u8>), String> {
    let mut sss = Command::new(get_shell())
        .arg("-c")
        .arg(cmd)
        .stdout(Stdio::piped())
        .spawn()
        .expect("failed to spawn shell command");

    match timeout(Duration::from_secs(secs), sss.wait()).await {
        Ok(Ok(ss)) => {
            let mut output = sss.stdout.take().unwrap();
            let mut buff = Vec::new();
            output
                .read_to_end(&mut buff)
                .await
                .expect("reading stdout failed");
            if let Some(code) = ss.code() {
                Ok((code, buff))
            } else {
                Err(format!("Run command filed: {}", cmd))
            }
        }
        Ok(Err(e)) => Err(format!("Run command filed,cmd {}, reason: {}", cmd, e)),
        Err(_) => {
            sss.kill().await.expect("failed to kill sss");
            Err(format!("Run command timeout: {}", cmd))
        }
    }
}

/// 探测命令是否存在 macos
#[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn probe_cmd(cmd: &String) -> bool {
    let result = std::process::Command::new(get_shell())
        .arg("-c")
        .arg(format!("{} {}", "type", cmd))
        .stdout(Stdio::null())
        .status();
    if result.is_ok() {
        result.unwrap().success()
    } else { false }
}

#[cfg(target_os = "windows")]
pub fn probe_cmd(cmd: &str) -> bool {
    let result = std::process::Command::new(get_shell())
        .arg("/C")
        .arg(format!("{} {}", "where", cmd))
        .stdout(Stdio::null())
        .status();
    if result.is_ok() {
        result.unwrap().success()
    } else { false }
}

#[test]
fn test_probe_cmd() {
    if probe_cmd(&"lsblk".to_string()) {
        println!("lsblk is exist");
    } else {
        println!("lsblk is not exist");
    }
    if probe_cmd(&"ls".to_string()) {
        println!("ls is exist");
    } else {
        println!("ls is not exist");
    }
    if probe_cmd(&"cmd".to_string()) {
        println!("cmd is exist");
    } else {
        println!("cmd is not exist");
    }
}
